/*  J
**  Date 2007-05-21 23:55:13 
**  File lex.c  v 1.0
**  Author Mucker
**  See Copyright Notice in J.h
*  
*/

#include "J.h"

#include "lex.h"
#include "io.h"
#include "tag.h"
#include "mem.h"
#include "object.h"

/* The reserved keyword table.  */

struct keyword
{
  const char *word;
  ENUM_BITFIELD(rid) const rid : 16;
  const unsigned int disable   : 16;
};
static struct keyword keywords[] =
{
	{"and"	, K_AND		, 0},
	{"break", K_BREAK	, 0},
	{"class", K_CLASS	, 0},
	{"else"	, K_ELSE	, 0},
	{"false", K_FALSE	, 0},
	{"if"	, K_IF		, 0},
	{"new"	, K_NEW		, 0},
	{"nil"	, K_NIL		, 0},
	{"or"	, K_OR		, 0},
	{"return", K_RETURN	, 0},
	{"true"	, K_TRUE	, 0},
	{"while", K_WHILE	, 0},
	{"="	, K_EQ	, 0},
	{">"	, K_GE	, 0},
	{"<"	, K_LE	, 0},
	{"::"	   , K_SPACE	, 0},
	{"."	   , K_CONCAT	, 0},
	{"..."	   , K_DOT		, 0},
	{"<string>", K_STRING	, 0},
	{"<number>", K_NUMBER	, 0},
	{"<name>"  , K_NAME		, 0},

	{"<eof>", K_EOF		, 0}

};


#define next(ls) ( ls->current = get_char(ls->jio) )
#define isnewline(ls) (ls->current == '\r' || ls->current == '\n') 

static int check_next(lex_state *ls, const char *str)
{
    if(!strchr(str, ls->current)) {
        return 0;
    }
    next(ls);
    return 1;
}

static void lex_error( lex_state *ls, const char *msg, int token)
{
    fprintf(stdout, "%s", msg);
}

static Jstring * lex_newtag(lex_state *ls, const char *str, size_t len)
{
	return tag_new(ls, str, len);
}

static void increase_line(lex_state *ls)
{
    int c = ls->current;
    next(ls);
    if(isnewline(ls) && ls->current != c)
        next(ls);
    ls->line++;
}

static void long_comment(lex_state *ls)
{
    next(ls);
    if(ls->current == EOF) 
        lex_error(ls, "unfinished long comment", 0); 
    for(;;){
        switch(ls->current){
            case '\r':
            case '\n':
                increase_line(ls);
                break;
            case '*':
                next(ls);
                if( ls->current != '/') continue;
                else {
                    next(ls);
                    goto end_long;
                }
            default :
                next(ls);
        }
    }
end_long:;
}

#define save_and_next(ls) save(ls, ls->current); next(ls)

static void save(lex_state *ls, int c)
{
    lex_buff *buff = ls->buff;
	buff->buffer[buff->len++] = cast(char, c);
}

static void read_number(lex_state *ls, token_info *info)
{
    do{
        save_and_next(ls);
    }while( isdigit(ls->current) || ls->current == '.');
    save(ls, '\0');
    obj_str2d(Mget_buff(ls->buff), &info->num);
}

static void read_string( lex_state *ls, int c, token_info *info)
{
    save_and_next(ls);
    while (c!=ls->current){
        switch(ls->current){
            case '\r':
            case '\n':
                save(ls, ls->current);
                increase_line(ls);
                continue;
            case EOF:
                lex_error(ls, "unfinished string", K_STRING);    
            case '\\': {
                int c;
                next(ls);
                switch(ls->current){
                    case 'a': c = '\a'; break;
                    case 'b': c = '\b'; break;
                    case 'f': c = '\f'; break;
                    case 'n': c = '\n'; break;
                    case 'r': c = '\r'; break;
                    case 't': c = '\t'; break;
                    case 'v': c = '\v'; break;
                    case '\\': c = '\\'; break;
                    case '\n':  /* go through */
                    case '\r': next(ls); increase_line(ls); continue;
                    case EOF: continue;  /* will raise an error next loop */
                    default:
                    break;
                }
                save(ls, c);
                next(ls);
                continue;
             }
             default:
                save_and_next(ls);
        }
    }
    save_and_next(ls);
    info->str = lex_newtag(ls, Mget_buff(ls->buff)+1, 
                                     Mlen_buff(ls->buff)-2);
}


int lex(lex_state *ls, token_info *info)
{
	Mreset_buff(ls->buff);
	for(;;){
		switch(ls->current){
            case '\r':
            case '\n':
                increase_line(ls);
                break;
            case '\t':
				next(ls);
                break;
            case '=':
                next(ls);
                if(ls->current != '=') return '=';
                next(ls);
                return K_EQ;
            case '>':
                next(ls);
                if(ls->current != '=') return '>';
                next(ls);
                return K_GE;
            case '<':
                next(ls);
                if(ls->current != '=') return '<';
                next(ls);
                return K_LE;
            case EOF :
                return K_EOF;
            case ':':
                next(ls);
                if(ls->current != ':') return ':';
                next(ls);
                return K_SPACE;
            case '/':                             /* short comment */              
                next(ls);
                if(ls->current == '/'){
                    while(!isnewline(ls) && ls->current != EOF){
                        next(ls);
                    }
                    continue;
                }
                else if(ls->current == '*') {       /* long comment */
                    long_comment(ls);
                    continue;
                }
                else return '/';
            case '.':
                next(ls);
                if(check_next(ls, ".")){
                    if(check_next(ls, ".")) return K_DOT;
                    else return  K_CONCAT;
                }
                else if (!isdigit(ls->current)) return '.';
                else {
                    return K_NUMBER;
                }
            case '"':
            case '\'':
                read_string(ls, ls->current, info);
                return K_STRING;
			default:{
				if(isspace(ls->current)){
					next(ls);
					continue;
				}
                else if(isdigit(ls->current)) {
                    read_number(ls, info);
                    return K_NUMBER;
                }
                else if(isalpha(ls->current) || ls->current == '_'){
                    Jstring *js;
                    do{
                        save_and_next(ls);
                    }while(isalnum(ls->current) || ls->current == '_');
                    js = lex_newtag(ls, Mget_buff(ls->buff), \
											Mlen_buff(ls->buff));
                    if(js->keyword >0 ){
                        return  js->keyword - 1;
                    }
                    info->str = js;
                    return K_NAME;
                }
                else { 
                    int c = ls->current; 
                    next(ls);
                    return c ; 
                }
			}
		}
	}
}


static void tab_test(Jtable *t, Jstring *Jstr)
{
	int n =0;
	tab_setstring(t, Jstr);

}


static void lex_inkey(lex_state *ls)
{
//	Jtable *t = tab_new(0);
	int n = 0;
	for(n; n< (K_WHILE-K_AND+1); n++){
		Jstring *Jstr;
		if(!keywords[n].disable)
			Jstr = tag_new(ls, keywords[n].word, strlen(keywords[n].word));
		Jstr->keyword = n + 1 +257;

//		tab_test(t, Jstr);
	}
//	exit(1);
}


void lex_init(lex_state *ls, JIO *jio)
{
	ls->jio = jio;
	ls->line = 0;
	ls->current = 0;

	ls->buff = Mmalloc(sizeof(lex_buff));	
	ls->ttag = tag_init(ls->ttag);

	lex_inkey(ls);
	
	next(ls);
}

void lex_out(lex_state *ls)
{
	ls->t.token = lex(ls, &ls->t.info);
}
void lex_ahead(lex_state *ls)
{
	ls->ahead.token = lex(ls, &ls->ahead.info);
}

